package org.karelun.whiteboard.edit.properties;

import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.UpdateValueStrategy;
import org.eclipse.core.databinding.conversion.IConverter;
import org.eclipse.core.databinding.conversion.NumberToStringConverter;
import org.eclipse.core.databinding.conversion.StringToNumberConverter;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.emf.databinding.EMFDataBindingContext;
import org.eclipse.emf.databinding.FeaturePath;
import org.eclipse.emf.databinding.edit.EMFEditProperties;
import org.eclipse.emf.databinding.edit.IEMFEditValueProperty;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.views.properties.tabbed.AbstractPropertySection;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.karelun.whiteboard.gef.EditDomain;
import org.karelun.whiteboard.gef.EditPart;

/**
 * @Author: KarelunHuang
 * @Date: Jan 30, 2013
 **/
public abstract class BasePropertySection<T extends EObject> extends AbstractPropertySection{
	
	private DataBindingContext bindingContext;
	
	private Map<ISWTObservableValue,IObservableValue> map;
	
	private T selectedObjectModel;
	
	@Override
	public void createControls(Composite parent,
			TabbedPropertySheetPage aTabbedPropertySheetPage) {
		super.createControls(parent, aTabbedPropertySheetPage);
		bindingContext=new EMFDataBindingContext();
		
		Section section = getWidgetFactory().createSection(parent, Section.TITLE_BAR|Section.TWISTIE|Section.EXPANDED);
		section.setText(getSectionTitle());
		section.setLayout(new FillLayout());
		
		Composite composite = getWidgetFactory().createComposite(section);
		composite.setLayout(new GridLayout());
		composite.setLayoutData(new GridData(GridData.FILL_BOTH));
		doCreateControl(composite);
		
		section.setClient(composite);
	}
	
	protected abstract String getSectionTitle();

	protected abstract void doCreateControl(Composite parent);

	@Override
	public void setInput(IWorkbenchPart part, ISelection selection) {
		super.setInput(part, selection);
		T select=getSelectedObjectModel(selection);
		if(select!=null&&selectedObjectModel!=select){
			selectedObjectModel=select;
			dataBinding();
		}
	}
	
	protected abstract void dataBinding();
	
	protected T getSelectedObjectModel(){
		return selectedObjectModel;
	}

	private T getSelectedObjectModel(ISelection selection) {
		if(selection instanceof IStructuredSelection){
			Object obj = ((IStructuredSelection)selection).getFirstElement();
			if(obj instanceof EditPart){
				Object model = ((EditPart)obj).getModel();
				return (T)model;
			}
		}
		return null;
	}
	
	protected void dataBinding(ISWTObservableValue controlObserver,EObject object,EStructuralFeature feature){
		dataBinding(controlObserver, object, feature,String.class);
	}
	
	protected void dataBinding(ISWTObservableValue controlObserver,EObject object,EStructuralFeature feature,Class<?> typeClass){
		dataBinding(controlObserver, object, feature,null,typeClass);
	}

	protected void dataBinding(ISWTObservableValue controlObserver,EObject object,EStructuralFeature feature,IValidator validator,Class<?> typeClass){
		if(map==null){
			map=new HashMap<ISWTObservableValue, IObservableValue>();
		}
		IObservableValue modelObserver = map.get(controlObserver);
		if(modelObserver!=null&&!modelObserver.isDisposed()){
			modelObserver.dispose();
			map.remove(controlObserver);
		}
		UpdateValueStrategy controlStrategy = createUpdateValueStrategy(typeClass,true);
		UpdateValueStrategy modelStrategy = createUpdateValueStrategy(typeClass,false);
		
		if(validator!=null){
			controlStrategy.setAfterConvertValidator(validator);
			modelStrategy.setAfterConvertValidator(validator);
		}
	    
		IEMFEditValueProperty value = EMFEditProperties.value(getEditingDomain(), feature);
		IObservableValue newObserver =value.observe(object);
		bindingContext.bindValue(controlObserver, newObserver,controlStrategy,modelStrategy);

		map.put(controlObserver, newObserver);
	}
	
	protected void dataBinding(ISWTObservableValue controlObserver,EObject object,FeaturePath featurePath){
		dataBinding(controlObserver, object, featurePath, null);
	}
	
	protected void dataBinding(ISWTObservableValue controlObserver,EObject object,FeaturePath featurePath,IValidator validator){
		dataBinding(controlObserver, object, featurePath, validator,null);
	}
	
	protected void dataBinding(ISWTObservableValue controlObserver,EObject object,FeaturePath featurePath,IValidator validator,Class<?> typeClass){
		if(map==null){
			map=new HashMap<ISWTObservableValue, IObservableValue>();
		}
		IObservableValue modelObserver = map.get(controlObserver);
		if(modelObserver!=null&&!modelObserver.isDisposed()){
			modelObserver.dispose();
			map.remove(controlObserver);
		}
		UpdateValueStrategy controlStrategy = createUpdateValueStrategy(typeClass,true);
		UpdateValueStrategy modelStrategy = createUpdateValueStrategy(typeClass,false);
		
		if(validator!=null){
			controlStrategy.setAfterConvertValidator(validator);
			modelStrategy.setAfterConvertValidator(validator);
		}
		IEMFEditValueProperty value = EMFEditProperties.value(getEditingDomain(), featurePath);
		IObservableValue newObserver =value.observe(object);
		bindingContext.bindValue(controlObserver, newObserver,controlStrategy,modelStrategy);

		map.put(controlObserver, newObserver);	
	}
	
	private UpdateValueStrategy createUpdateValueStrategy(Class<?> typeClass,boolean controlOrModel) {
		UpdateValueStrategy strategy = new UpdateValueStrategy();
		if(typeClass==null){
			return strategy;
		}
		IConverter converter = createConverter(typeClass, controlOrModel);
		if(converter!=null){
			strategy.setConverter(converter);
		}
		return strategy;
	}
	
	private IConverter createConverter(Class<?> typeClass,boolean controlOrModel){
		if(typeClass==Short.class){
			if(controlOrModel){
				return StringToNumberConverter.toShort(true);
			}
			return NumberToStringConverter.fromShort(true);
		}else if(typeClass==Integer.class){
			if(controlOrModel){
				return StringToNumberConverter.toInteger(true);
			}
			return NumberToStringConverter.fromInteger(true);
		}else if(typeClass==Float.class){
			if(controlOrModel){
				return StringToNumberConverter.toFloat(true);
			}
			return NumberToStringConverter.fromFloat(true);
		}else if(typeClass==Long.class){
			if(controlOrModel){
				return StringToNumberConverter.toLong(true);
			}
			return NumberToStringConverter.fromLong(true);
		}else if(typeClass==Double.class){
			if(controlOrModel){
				return StringToNumberConverter.toDouble(true);
			}
			return NumberToStringConverter.fromDouble(true);
		}else if(typeClass==BigInteger.class){
			if(controlOrModel){
				return StringToNumberConverter.toBigInteger();
			}
			return NumberToStringConverter.fromBigInteger();
		}
		return null;
	}
	
	protected EditingDomain getEditingDomain(){
		EditDomain editDomain = (EditDomain)getPart().getAdapter(EditDomain.class);
		return editDomain.getEditingDomain();
	}

	protected boolean isEditable(){
		EditDomain editDomain = (EditDomain)getPart().getAdapter(EditDomain.class);
		return editDomain.isEditable();
	}
	
	@Override
	public void dispose() {
		if(map!=null){
			map.clear();
			map=null;
		}
		if(bindingContext!=null){
			bindingContext.dispose();
			bindingContext=null;
		}
	}
}
